#!/usr/bin
# -*- coding: utf-8 -*-

import re
import os

ABAQUS_ELEMENT_PATTERN = r"(.*),(.*),(.*)"
ABAQUS_NODE_PATTERN = r"(.*),(.*),(.*),(.*)"

NASTRAN_ELEMENT_PATTERN = r"crod     (........)(........)(........)(.*)"
NASTRAN_NODE_PATTERN = r"grid     (................)(........)(........)(.*)"

GMSH_ELEMENT_PATTERN = r"line\((.*)\)=\{(.*),(.*)\};"
GMSH_NODE_PATTERN = r"point\((.*)\)=\{(.*),(.*),(.*)\};"

NASTRAN_GEOM_POINT_PATTERN = r'asm_const_grid_xyz\( "(.*)", "\[(.*) (.*) (.*)\]", "Coord 0", asm_create_grid_xyz_created_ids \)'
NASTRAN_GEOM_LINE_PATTERN = r'asm_const_line_2point\( "(.*)", "Point (.*)", "Point (.*)", 0, "", 50., 1,  @'

BOUNDARY_NODE_PATTERN = r'noeud,(.*),(.*),(.*),(.*)'
BOUNDARY_ELEMENT_PATTERN = r'element,(.*),(.*),(.*),(.*)'

VTK_ELEMENT_PATTERN = r"(.*) (.*) (.*)"
VTK_NODE_PATTERN = r"(.*) (.*) (.*)"

CODES = {'inp':'abaqus',
         'bdf':'nastran',
         'geo':'gmsh',
         'ses':'nastranGeom',
         'bnd':'boundary',
         'vtk':'vtk'}




# Fonctions pour BOUNDARY ------------------------------------------------------
def readBoundaryLine(line, inElem, inNode, keyWord):

    # Recherche pour une ligne de noeuds
    if re.search(BOUNDARY_NODE_PATTERN, line):
        inElem, inNode, keyWord = False, True, False
        match = re.search(BOUNDARY_NODE_PATTERN, line)

        data = int(match.group(1)), float(match.group(2)), \
                                    float(match.group(3)), \
                                    float(match.group(4))

    # Recherche pour une ligne d'éléments
    elif re.search(BOUNDARY_ELEMENT_PATTERN, line):
        inElem, inNode, keyWord = True, False, False
        match = re.search(BOUNDARY_ELEMENT_PATTERN, line)

        data = int(match.group(1)), int(match.group(2)), \
                                    int(match.group(3)), \
                                    int(match.group(4)), 0 # 0 pour le matériau

    else:
        raise ValueError, "Ligne non conforme dans le fichier Boundary"

    return (data, inElem, inNode, keyWord)



def writeBoundaryLine(myFile, data, inElem, inNode, allReadyKeyWord):
    """ ------------------------------------------------------------------------
    data est une liste ou un tuple
    data = [ nodeId, nodeX, nodeY, nodeZ ] si inNode
    data = [ elementId, elementNode1, elementNode2, elementNode3 ] si inElement
    ----------------------------------------------------------------------------
    """
    if inNode and inElem:
        raise ValueError, "inElem ou inNode, un seul des deux doit être vrai"
    elif inNode:
        line = "noeud, "
        line += str(data[0]) + ", "
        line += str(data[1]) + ", " # x
        line += str(data[2]) + ", " # y
        line += str(data[3]) + "\n" # z
    elif inElem:
        line = "element, "
        line += str(data[0]) + ", "
        line += str(data[1]) + ", " # noeud 1
        line += str(data[2]) + ", " # noeud 2
        line += str(data[3]) + "\n" # noeud 3

    else:
        raise ValueError, "inElem < ou exclusif > inNode doit être vrai"

    myFile.write(line)
    return allReadyKeyWord






# Fonctions pour Nastran Geom --------------------------------------------------

def readNastranGeomLine(line, inElem, inNode, keyWord):

    # Nous sommes en train de lire des noeuds, deux cas de figures :
    # - la ligne est bien un noeud
    # - la ligne est un mot clef

    # ==========================================================================
    # Le test sur les mots clefs n'est ici pas indispensable car les lignes
    # de noeuds et les lignes d'éléments diffèrent en format
    # Id, x, y, z || Id, n1, n2
    # mais je l'ai quand même programmé comme ça pour le cas général où
    # l'on aurait 3 noeuds par élément ce qui empêcherai de distinguer
    # noeuds et élément par le seul format de la ligne
    # ==========================================================================

    if line[:2] == "$#":
        pass
    elif line[:29] == 'asm_line_2point_created_ids )':
        pass
    elif line[:43] == "string asm_line_2point_created_ids[virtual]":
        data, inElem, inNode, keyWord = None, True, False, True

    elif line[:47] == "string asm_create_grid_xyz_created_ids[virtual]":
        data, inElem, inNode, keyWord = None, False, True, True

    elif re.search(NASTRAN_GEOM_POINT_PATTERN, line):
        inElem, inNode, keyWord = False, True, False
        match = re.search(NASTRAN_GEOM_POINT_PATTERN, line)

        data = int(match.group(1)), float(match.group(2)), \
                                    float(match.group(3)), \
                                    float(match.group(4))

    elif re.search(NASTRAN_GEOM_LINE_PATTERN, line):
        inElem, inNode, keyWord = True, False, False
        match = re.search(NASTRAN_GEOM_LINE_PATTERN, line)

        data = int(match.group(1)), int(match.group(2)), \
                                    int(match.group(3)), 0 # 0 pour le matériau

    else:
        raise ValueError, "Ligne non conforme dans le fichier Nastran Geom"

    return (data, inElem, inNode, keyWord)




def writeNastranGeomLine(myFile, data, inElem, inNode, allReadyKeyWord):
    if inNode and inElem:
        raise ValueError, "inElem ou inNode, un seul des deux doit être vrai"
    elif inNode:
        if allReadyKeyWord:
            line = 'asm_const_grid_xyz( "'
            line += str(data[0]) + '", "['
            line += str(data[1]) + ' '
            line += str(data[2]) + ' '
            line += str(data[3]) + ']", "Coord 0", asm_create_grid_xyz_created_ids )\n'
        else:
            line = "STRING asm_create_grid_xyz_created_ids[VIRTUAL]\n"
            line += 'asm_const_grid_xyz( "'
            line += str(data[0]) + '", "['
            line += str(data[1]) + ' '
            line += str(data[2]) + ' '
            line += str(data[3]) + ']", "Coord 0", asm_create_grid_xyz_created_ids )\n'
            allReadyKeyWord = True

    elif inElem:
        if allReadyKeyWord:
            line = 'asm_const_line_2point( "'
            line += str(data[0]) + '", "Point '
            line += str(data[1]) + '", "Point '
            line += str(data[2]) + '", 0, "", 50., 1, asm_line_2point_created_ids )\n'
            #line += str(data[2]) + '", 0, "", 50., 1,  @\n'
            #line += 'asm_line_2point_created_ids )\n'
        else:
            line = "STRING asm_line_2point_created_ids[VIRTUAL]\n"
            line += 'asm_const_line_2point( "'
            line += str(data[0]) + '", "Point '
            line += str(data[1]) + '", "Point '
            line += str(data[2]) + '", 0, "", 50., 1, asm_line_2point_created_ids )\n'
            #line += str(data[2]) + '", 0, "", 50., 1,  @\n'
            #line += 'asm_line_2point_created_ids )\n'
            allReadyKeyWord= True

    else:
        raise ValueError, "inElem < ou exclusif > inNode doit être vrai"

    myFile.write(line)
    return allReadyKeyWord





# Fonctions pour ABAQUS --------------------------------------------------------

def readAbaqusLine(line, inElem, inNode, keyWord):

    # Nous sommes en train de lire des noeuds, deux cas de figures :
    # - la ligne est bien un noeud
    # - la ligne est un mot clef

    # ==========================================================================
    # Le test sur les mots clefs n'est ici pas indispensable car les lignes
    # de noeuds et les lignes d'éléments diffèrent en format
    # Id, x, y, z || Id, n1, n2
    # mais je l'ai quand même programmé comme ça pour le cas général où
    # l'on aurait 3 noeuds par élément ce qui empêcherai de distinguer
    # noeuds et élément par le seul format de la ligne
    # ==========================================================================

    if line[:8] == "*element":
        data, inElem, inNode, keyWord = None, True, False, True

    elif line[:5] == "*node":
        data, inElem, inNode, keyWord = None, False, True, True

    elif re.search(ABAQUS_NODE_PATTERN, line):
        inElem, inNode, keyWord = False, True, False
        match = re.search(ABAQUS_NODE_PATTERN, line)

        data = int(match.group(1)), float(match.group(2)), \
                                    float(match.group(3)), \
                                    float(match.group(4))

    elif re.search(ABAQUS_ELEMENT_PATTERN, line):
        inElem, inNode, keyWord = True, False, False
        match = re.search(ABAQUS_ELEMENT_PATTERN, line)

        data = int(match.group(1)), int(match.group(2)), \
                                    int(match.group(3)), 0 # 0 pour le matériau

    else:
        raise ValueError, "Ligne non conforme dans le fichier Abaqus"

    return (data, inElem, inNode, keyWord)




def writeAbaqusLine(myFile, data, inElem, inNode, allReadyKeyWord):
    if inNode and inElem:
        raise ValueError, "inElem ou inNode, un seul des deux doit être vrai"
    elif inNode:
        if allReadyKeyWord:
            line = str(data[0]) + "," + str(data[1]) \
                                + "," + str(data[2]) \
                                + "," + str(data[3]) + "\n"
        else:
            line = "*node\n"
            line += str(data[0]) + "," + str(data[1]) \
                                 + "," + str(data[2]) \
                                 + "," + str(data[3]) + "\n"
            allReadyKeyWord = True

    elif inElem:
        if allReadyKeyWord:
            line = str(data[0]) + "," + str(data[1]) \
                                + "," + str(data[2]) + "\n"
        else:
            line = "*element, type=T3D2\n"
            line += str(data[0]) + "," + str(data[1]) \
                                 + "," + str(data[2]) + "\n"
            allReadyKeyWord= True

    else:
        raise ValueError, "inElem < ou exclusif > inNode doit être vrai"

    myFile.write(line)
    return allReadyKeyWord



# Fonctions pour NASTRAN -------------------------------------------------------
def readNastranLine(line, inElem, inNode, keyWord):

    # Recherche pour une ligne de noeuds
    if re.search(NASTRAN_NODE_PATTERN, line):
        inElem, inNode, keyWord = False, True, False
        match = re.search(NASTRAN_NODE_PATTERN, line)

        data = int(match.group(1)), float(match.group(2)), \
                                    float(match.group(3)), \
                                    float(match.group(4))

    # Recherche pour une ligne d'éléments
    elif re.search(NASTRAN_ELEMENT_PATTERN, line):
        inElem, inNode, keyWord = True, False, False
        match = re.search(NASTRAN_ELEMENT_PATTERN, line)

        data = int(match.group(1)), int(match.group(3)), \
                                    int(match.group(4)), \
                                    match.group(2) # le matériau

    else:
        raise ValueError, "Ligne non conforme dans le fichier Nastran"

    return (data, inElem, inNode, keyWord)



def writeNastranLine(myFile, data, inElem, inNode, allReadyKeyWord):
    """ ------------------------------------------------------------------------
    data est une liste ou un tuple
    data = [ nodeId, nodeX, nodeY, nodeZ ] si inNode
    data = [ elementId, elementNode1, elementNode2 ] si inElement
    ----------------------------------------------------------------------------
    """
    if inNode and inElem:
        raise ValueError, "inElem ou inNode, un seul des deux doit être vrai"
    elif inNode:
        line = "GRID     "
        line += str(data[0]) + " " * (16 - len(str(data[0])))
        x1 = "%+.3f" % data[1]
        x2 = "%+.3f" % data[2]
        x3 = "%+.3f" % data[3]
        line += x1[:8] + " " * (8 - len(x1[:8]))
        line += x2[:8] + " " * (8 - len(x2[:8]))
        line += x3[:8] + "\n"
    elif inElem:
        line = "CROD     "
        line += str(data[0]) + " " * (8 - len(str(data[0])))
        line += str(data[-1]) + " " * (8 - len(str(data[-1])))
        line += str(data[1]) + " " * (8 - len(str(data[1])))
        line += str(data[2]) + "\n"

    else:
        raise ValueError, "inElem < ou exclusif > inNode doit être vrai"

    myFile.write(line)
    return allReadyKeyWord



# Fonctions pour GMSH ----------------------------------------------------------
def readGmshLine(line, inElem, inNode, keyWord):
    if re.search(GMSH_NODE_PATTERN, line):
        match = re.search(GMSH_NODE_PATTERN, line)
        data = int(match.group(1)), float(match.group(2)), \
                                    float(match.group(3)), \
                                    float(match.group(4))
        inElem, inNode, keyWord = False, True, False

    elif re.search(GMSH_ELEMENT_PATTERN, line):
        match = re.search(GMSH_ELEMENT_PATTERN, line)
        data = int(match.group(1)), int(match.group(2)), \
                                    int(match.group(3)), 0 # 0 pour le matériau
        inElem, inNode, keyWord = True, False, False

    else:
        raise ValueError, "Ligne non conforme dans le fichier GMSH"

    return (data, inElem, inNode, keyWord)



def writeGmshLine(myFile, data, inElem, inNode, allReadyKeyWord):
    """ ------------------------------------------------------------------------
    data est une liste ou un tuple
    data = [ nodeId, nodeX, nodeY, nodeZ ] si inNode
    data = [ elementId, elementNode1, elementNode2 ] si inElement
    ----------------------------------------------------------------------------
    """
    if inNode and inElem:
        raise ValueError, "inElem ou inNode, un seul des deux doit être vrai"
    elif inNode:
        line = "Point(" + str(data[0]) + ")={" + str(data[1]) + "," \
                                               + str(data[2]) + "," \
                                               + str(data[3]) + "};\n"
    elif inElem:
        line = "Line(" + str(data[0]) + ")={" + str(data[1]) + "," \
                                              + str(data[2]) + "};\n"

    else:
        raise ValueError, "inElem < ou exclusif > inNode doit être vrai"

    myFile.write(line)
    return allReadyKeyWord




# Fonctions pour VTK/PARAVIEW --------------------------------------------------
def readVTKLine(line, inElem, inNode, keyWord):

    # Nous sommes en train de lire des noeuds, deux cas de figures :
    # - la ligne est bien un noeud
    # - la ligne est un mot clef

    # ==========================================================================
    # Le test sur les mots clefs n'est ici pas indispensable car les lignes
    # de noeuds et les lignes d'éléments diffèrent en format
    # Id, x, y, z || Id, n1, n2
    # mais je l'ai quand même programmé comme ça pour le cas général où
    # l'on aurait 3 noeuds par élément ce qui empêcherai de distinguer
    # noeuds et élément par le seul format de la ligne
    # ==========================================================================

    nodeId = 0
    elementId = 0

    if line[:5].lower() == "lines":
        data, inElem, inNode, keyWord = None, True, False, True

    elif line[:6].lower() == "points":
        data, inElem, inNode, keyWord = None, False, True, True

    elif re.search(VTK_NODE_PATTERN, line):
        inElem, inNode, keyWord = False, True, False
        match = re.search(VTK_NODE_PATTERN, line)

        data = int(nodeId), float(match.group(2)), \
                            float(match.group(3)), \
                            float(match.group(4))
        nodeId += 1

    elif re.search(VTK_ELEMENT_PATTERN, line):
        inElem, inNode, keyWord = True, False, False
        match = re.search(VTK_ELEMENT_PATTERN, line)

        data = int(elementId), int(match.group(2)), \
                               int(match.group(3)), 0 # 0 pour le matériau
        elementId += 1

    else:
        raise ValueError, "Ligne non conforme dans le fichier VTK"

    return (data, inElem, inNode, keyWord)




def writeVTKLine(myFile, data, inElem, inNode, allReadyKeyWord):
    raise NotImplementedError, "Syntaxe trop différente pour être traitée " + \
                               "les autres fichiers en écriture"



def bdf2vtk(bdfFileName, vtkFileName):
    nodes, elements, materials = readWholeFile(bdfFileName)

    nNodes = len(nodes)
    nElements = len(elements)

    # Sorting nodes and elements
    nodeIds = nodes.keys()
    nodeIds.sort()
    nodeCoords = map(nodes.get, nodeIds)

    elementIds = elements.keys()
    elementIds.sort()
    elementNodes = map(elements.get, elementIds)

    nodesMap = {}
    for i, nodeId in enumerate(nodeIds):
        nodesMap[nodeId] = i

    try:
        myFile = open(vtkFileName, "w")
    except:
        raise ValueError, "Problem while creating the file " + outputFileName + "..."

    myFile.write("# vtk DataFile Version 2.0\n")
    myFile.write("Foam mesh from EurICA Team\n")
    myFile.write("ASCII\n")

    myFile.write("\n")
    myFile.write("DATASET POLYDATA\n")
    myFile.write("POINTS " + str(nNodes) + " float\n")

    for x, y, z in nodeCoords:
        myFile.write(str(x) + " " + \
                     str(y) + " " + \
                     str(z) + "\n")

    myFile.write("\n")
    myFile.write("LINES " + str(nElements) + " " + str(3*nElements) + "\n")

    for n1, n2 in elementNodes:
        node1 = nodesMap[n1]
        node2 = nodesMap[n2]

        myFile.write("2 " + str(node1) + " " \
                          + str(node2) + "\n")
    myFile.close()




def writeVTKFile(vtkFileName, nodes, elements, materials):

    del(materials) # saves RAM

    nNodes = len(nodes)
    nElements = len(elements)

    # Sorting nodes and elements
    nodeIds = nodes.keys()
    nodeIds.sort()
    nodeCoords = map(nodes.get, nodeIds)

    del(nodes) # saves RAM

    elementIds = elements.keys()
    elementIds.sort()
    elementNodes = map(elements.get, elementIds)

    del(elements) # saves RAM

    nodesMap = {}
    for i, nodeId in enumerate(nodeIds):
        nodesMap[nodeId] = i

    try:
        myFile = open(vtkFileName, "w")
    except:
        raise ValueError, "Problem while creating the file " + outputFileName + "..."

    myFile.write("# vtk DataFile Version 2.0\n")
    myFile.write("Foam mesh from EurICA Team\n")
    myFile.write("ASCII\n")

    myFile.write("\n")
    myFile.write("DATASET POLYDATA\n")
    myFile.write("POINTS " + str(nNodes) + " float\n")

    for x, y, z in nodeCoords:
        myFile.write(str(x) + " " + \
                     str(y) + " " + \
                     str(z) + "\n")

    myFile.write("\n")
    myFile.write("LINES " + str(nElements) + " " + str(3*nElements) + "\n")

    for n1, n2 in elementNodes:
        node1 = nodesMap[n1]
        node2 = nodesMap[n2]

        myFile.write("2 " + str(node1) + " " \
                          + str(node2) + "\n")
    myFile.close()




# INTERFACE DES FONCTIONS ======================================================
readFunctions = {"abaqus":readAbaqusLine,
                 "nastran":readNastranLine,
                 "gmsh":readGmshLine,
                 "nastranGeom":readNastranGeomLine,
                 "boundary":readBoundaryLine,
                 "vtk":readVTKLine}


writeFunctions = {"abaqus":writeAbaqusLine,
                 "nastran":writeNastranLine,
                 "gmsh":writeGmshLine,
                 "nastranGeom":writeNastranGeomLine,
                 "boundary":writeBoundaryLine,
                 "vtk":writeVTKLine}


# Fonctions d'interface de lecture et d'écriture -------------------------------
def readLine(code, line, inElem, inNode, keyWord):
    line = line.lower()
    line = line.replace("\n", "")
    if readFunctions.has_key(code):
        data, inElem, inNode, keyWord = \
            readFunctions[code](line, inElem, inNode, keyWord)
    else:
        raise ValueError, "code must be in " + str(CODES)
    return (data, inElem, inNode, keyWord)



def writeLine(code, myFile, data, inElem, inNode, allReadyKeyWord):
    if writeFunctions.has_key(code):
        allReadyKeyWord = \
            writeFunctions[code](myFile, data, inElem, inNode, allReadyKeyWord)
    else:
        raise ValueError, "code must be in " + str(CODES)
    return allReadyKeyWord



# Fonctions d'import/export de fichiers entiers --------------------------------
def readWholeFile(inputFileName):

    code = inputFileName.lower().split(".")
    if not code[-1] in CODES.keys():
        raise ValueError, "code must be in " + str(CODES)
    code = CODES[code[-1]]

    try:
        myFile = open(inputFileName, "r")
    except:
        raise IOError, "Unable to open the selected file" + inputFileName

    nodes = {}
    elements = {}
    materials = {}

    while True:
        line = myFile.readline()
        if not line:
            break

        data, inElem, inNode, keyWord = \
           readLine(code, line, False, False, False)
        if not keyWord:
            if inElem:
                if len(data) == 4:
                    elements[data[0]] = [data[1], data[2]]
                elif len(data) == 5:
                    elements[data[0]] = [data[1], data[2], data[3]]
                materials[data[0]] = data[-1]
            elif inNode:
                nodes[data[0]] = (data[1], data[2], data[3])
    myFile.close()
    return nodes, elements, materials



def writeWholeFile(outputFileName, nodes, elements, materials):

    code = outputFileName.lower().split(".")
    if not code[-1] in CODES.keys():
        raise ValueError, "code must be in " + str(CODES)
    code = CODES[code[-1]]

    try:
        myFile = open(outputFileName, "w")
    except:
        raise IOError, "Impossible de créer le fichier " + outputFileName

    allReadyKeyWord = False
    for key, value in nodes.items():
        data = (key, value[0], value[1], value[2])
        allReadyKeyWord = writeLine(code, myFile, data, False, True, allReadyKeyWord)

    allReadyKeyWord = False
    for key, value in elements.items():
        if len(value) == 2:
            data = (key, value[0], value[1], materials[key])
        elif len(value) == 3:
            data = (key, value[0], value[1], value[2], materials[key])
        allReadyKeyWord = writeLine(code, myFile, data, True, False, allReadyKeyWord)

    myFile.close()


